use super::ButtonFactory;
use crate::chess::*;
use eframe::{self, egui};
use std::path::PathBuf;

pub struct EditorApp {
    // 己方，显示在屏幕下方
    ownside: Color,
    pub session: Session,
    pieces: ButtonFactory<(Color, Piece)>,
    pieces_mirror: ButtonFactory<(Color, Piece)>,
    buttons: ButtonFactory<&'static str>,
    selected_piece: Option<Pos>,
    piece_moved: Option<u32>,
    frame_counter: u32,
    show_next_steps: bool,
    show_score: bool,
    pub is_menu_open: bool,
    pub is_dialog_open: bool,
    in_mirror_mode: bool,
    pub book_file: Option<PathBuf>,
    pub situation_name: String,
    pub cursor_image: Option<egui::TextureHandle>,
}

impl EditorApp {
    pub fn new(
        book: Book,
        book_file: Option<PathBuf>,
        start: Option<SituationId>,
        turn: Option<Color>,
    ) -> Self {
        EditorApp {
            ownside: Color::Red,
            session: Session::new(book, start, turn),
            pieces: ButtonFactory::new(false),
            pieces_mirror: ButtonFactory::new(false),
            buttons: ButtonFactory::new(true),
            selected_piece: None,
            piece_moved: None,
            frame_counter: 0,
            show_next_steps: true,
            show_score: false,
            is_menu_open: false,
            is_dialog_open: false,
            in_mirror_mode: false,
            book_file,
            situation_name: String::new(),
            cursor_image: None,
        }
    }

    pub fn name(&self) -> &str {
        &self.session.book.name
    }

    // Called once before the first frame.
    pub fn setup(&mut self, ctx: &egui::Context) {
        let piece_buttons = vec![
            ((Color::Red, Piece::Pawn), 6, 6, 162, 162),
            ((Color::Red, Piece::Cannon), 184, 6, 162, 162),
            ((Color::Red, Piece::General), 364, 6, 162, 162),
            ((Color::Red, Piece::Rook), 6, 180, 162, 162),
            ((Color::Red, Piece::Horse), 184, 180, 162, 162),
            ((Color::Red, Piece::Elephant), 366, 180, 162, 162),
            ((Color::Red, Piece::Advisor), 542, 180, 162, 162),
            ((Color::Black, Piece::Pawn), 6, 540, 162, 162),
            ((Color::Black, Piece::Cannon), 184, 540, 162, 162),
            ((Color::Black, Piece::General), 364, 540, 162, 162),
            ((Color::Black, Piece::Rook), 6, 360, 162, 162),
            ((Color::Black, Piece::Horse), 184, 360, 162, 162),
            ((Color::Black, Piece::Elephant), 366, 360, 162, 162),
            ((Color::Black, Piece::Advisor), 542, 360, 162, 162),
        ];
        let piece_image = include_bytes!("../../assets/chess_pieces.qoi");

        self.pieces
            .initialize(ctx, "piece", piece_image, &piece_buttons);

        self.pieces_mirror
            .initialize_mirrored(ctx, "piece_mirror", piece_image, &piece_buttons);

        self.buttons.initialize(
            ctx,
            "buttons",
            include_bytes!("../../assets/buttons.qoi"),
            &[
                ("menu", 5, 10, 123, 58),
                ("start", 133, 10, 123, 58),
                ("prev", 262, 10, 152, 58),
                ("next", 420, 10, 153, 58),
                ("branch", 579, 10, 182, 58),
            ],
        );

        let mut fonts = egui::FontDefinitions::default();
        fonts.font_data.insert(
            "kaiti".to_owned(),
            egui::FontData::from_static(include_bytes!("../../assets/kaiti.ttf")),
        );
        fonts
            .families
            .get_mut(&egui::FontFamily::Proportional)
            .unwrap()
            .insert(0, "kaiti".to_owned());
        ctx.set_fonts(fonts);

        let (header, image_data) =
            rapid_qoi::Qoi::decode_alloc(include_bytes!("../../assets/cursor.qoi")).unwrap();
        let (width, height) = (header.width as usize, header.height as usize);
        let color_image = egui::ColorImage::from_rgba_unmultiplied([width, height], &image_data);
        let cursor_image = ctx.load_texture("cursor", color_image, egui::TextureOptions::LINEAR);
        self.cursor_image = Some(cursor_image);
    }

    pub fn load_book(&mut self, book: Book) {
        self.session = Session::new(book, None, None);
        self.selected_piece = None;
        self.piece_moved = None;
    }

    fn board_pos_to_image_pos(&self, pos: Pos, board_start: f32, grid_size: f32) -> egui::Pos2 {
        let offset = grid_size / 2.0;
        let (row, col) = pos.row_col();
        let left = board_start + offset + (col as f32) * grid_size;
        let top = offset + (row as f32) * grid_size;
        egui::Pos2::new(left, top)
    }

    fn piece_pos_to_image_pos(&self, pos: Pos, board_start: f32, grid_size: f32) -> egui::Pos2 {
        let mut pos = pos.clone();
        if self.ownside == Color::Black {
            // rotate board vertically
            pos = Pos(89 - pos.0);
        }
        if self.in_mirror_mode {
            // mirror board horizontally
            let (row, col) = pos.row_col();
            pos = Pos::from_row_col((row, 8 - col)).unwrap();
        }
        self.board_pos_to_image_pos(pos, board_start, grid_size)
    }

    fn get_piece_button(&self, color: Color, piece: Piece, piece_size: f32) -> egui::ImageButton {
        if self.in_mirror_mode {
            self.pieces_mirror.get_button((color, piece), piece_size)
        } else {
            self.pieces.get_button((color, piece), piece_size)
        }
    }

    fn get_piece_image(&self, color: Color, piece: Piece, piece_size: f32) -> egui::Image {
        if self.in_mirror_mode {
            self.pieces_mirror.get_image((color, piece), piece_size)
        } else {
            self.pieces.get_image((color, piece), piece_size)
        }
    }

    fn get_button(&self, name: &'static str, height: f32) -> egui::ImageButton {
        self.buttons.get_button(name, height)
    }

    fn draw_chess_board(&self, painter: &egui::Painter, board_start: f32, grid_size: f32) {
        let offset_x = board_start + grid_size * 0.5;
        let offset_y = grid_size * 0.5;
        let width = grid_size * 8.0;
        let half_height = grid_size * 4.0;
        // draw grid lines
        let stroke = egui::Stroke::new(2.0, egui::Color32::LIGHT_RED);
        painter.rect_stroke(
            egui::Rect::from_min_size(
                egui::Pos2::new(offset_x - grid_size * 0.05, offset_y - grid_size * 0.05),
                egui::Vec2::new(grid_size * 8.1, grid_size * 9.1),
            ),
            egui::Rounding::none(),
            egui::Stroke::new(4.0, stroke.color),
        );
        painter.rect_stroke(
            egui::Rect::from_min_size(
                egui::Pos2::new(offset_x, offset_y),
                egui::Vec2::new(grid_size * 8.0, grid_size * 9.0),
            ),
            egui::Rounding::none(),
            stroke,
        );
        for row in 1..9 {
            let y = offset_y + grid_size * (row as f32);
            painter.line_segment(
                [
                    egui::Pos2::new(offset_x, y),
                    egui::Pos2::new(offset_x + width, y),
                ],
                stroke,
            );
        }
        for col in 1..8 {
            let x = offset_x + grid_size * (col as f32);
            painter.line_segment(
                [
                    egui::Pos2::new(x, offset_y),
                    egui::Pos2::new(x, offset_y + half_height),
                ],
                stroke,
            );
            let offset = offset_y + grid_size * 5.0;
            painter.line_segment(
                [
                    egui::Pos2::new(x, offset),
                    egui::Pos2::new(x, offset + half_height),
                ],
                stroke,
            );
        }
        // draw cross
        for (start, end) in [(86, 66), (68, 84), (23, 3), (5, 21)] {
            painter.line_segment(
                [
                    self.board_pos_to_image_pos(Pos(start), board_start, grid_size),
                    self.board_pos_to_image_pos(Pos(end), board_start, grid_size),
                ],
                stroke,
            );
        }
        // draw initial positions
        let d = grid_size * 0.05;
        let m = d * 3.0;
        for pos in [64, 70, 56, 58, 60, 29, 31, 33, 19, 25] {
            let egui::Pos2 { x, y } = self.board_pos_to_image_pos(Pos(pos), board_start, grid_size);
            for (x1, y1, x2, y2) in [
                (x - d, y - d, x - m, y - d),
                (x - d, y - d, x - d, y - m),
                (x + d, y - d, x + d, y - m),
                (x + d, y - d, x + m, y - d),
                (x - d, y + d, x - m, y + d),
                (x - d, y + d, x - d, y + m),
                (x + d, y + d, x + d, y + m),
                (x + d, y + d, x + m, y + d),
            ] {
                painter.line_segment([egui::Pos2::new(x1, y1), egui::Pos2::new(x2, y2)], stroke);
            }
        }
        for pos in [54, 27] {
            let egui::Pos2 { x, y } = self.board_pos_to_image_pos(Pos(pos), board_start, grid_size);
            for (x1, y1, x2, y2) in [
                (x + d, y - d, x + d, y - m),
                (x + d, y - d, x + m, y - d),
                (x + d, y + d, x + d, y + m),
                (x + d, y + d, x + m, y + d),
            ] {
                painter.line_segment([egui::Pos2::new(x1, y1), egui::Pos2::new(x2, y2)], stroke);
            }
        }
        for pos in [62, 35] {
            let egui::Pos2 { x, y } = self.board_pos_to_image_pos(Pos(pos), board_start, grid_size);
            for (x1, y1, x2, y2) in [
                (x - d, y - d, x - m, y - d),
                (x - d, y - d, x - d, y - m),
                (x - d, y + d, x - m, y + d),
                (x - d, y + d, x - d, y + m),
            ] {
                painter.line_segment([egui::Pos2::new(x1, y1), egui::Pos2::new(x2, y2)], stroke);
            }
        }
    }

    fn move_piece(&mut self, from: Pos, to: Pos) {
        if self.session.try_move(from, to) {
            self.piece_moved = Some(self.frame_counter);
        } else {
            println!("Forbidden move");
        }
        self.selected_piece = None;
    }

    pub fn update_window_title(&self, frame: &mut eframe::Frame) {
        frame.set_window_title(&format!(
            "{}(回合数:{}){}{}",
            self.name(),
            self.session.steps.len() / 2,
            if self.in_mirror_mode {
                " 镜像模式"
            } else {
                ""
            },
            if self.session.book_changed { "*" } else { "" },
        ));
    }

    pub fn open_book_file(&mut self, frame: &mut eframe::Frame) -> bool {
        if self.session.book_changed {
            if !super::show_confirm_message("确认", "棋谱已更改，是否继续打开？") {
                return false;
            }
        }
        if let Some(file) = rfd::FileDialog::new()
            .add_filter("棋谱文件", &["xqb"])
            .pick_file()
        {
            match Book::load_from_text(&file) {
                Ok(book) => {
                    self.load_book(book);
                    self.update_window_title(frame);
                    self.book_file = Some(file);
                    return true;
                }
                Err(err) => super::show_error_message("错误", &format!("{}", err)),
            }
        }
        return false;
    }

    pub fn save_book_file(&mut self, frame: Option<&mut eframe::Frame>) -> bool {
        let mut saved = false;
        if let Some(file) = self.book_file.as_ref() {
            self.session
                .book
                .save_as_text(file)
                .unwrap_or_else(|err| super::show_error_message("错误", &format!("{}", err)));
            saved = true;
        } else if let Some(file) = rfd::FileDialog::new()
            .add_filter("棋谱文件", &["xqb"])
            .save_file()
        {
            self.session.book.name = file.file_stem().unwrap().to_string_lossy().to_string();
            self.session
                .book
                .save_as_text(&file)
                .unwrap_or_else(|err| super::show_error_message("错误", &format!("{}", err)));
            self.book_file = Some(file);
            saved = true;
        }
        if saved {
            self.session.book_changed = false;
            if let Some(frame) = frame {
                self.update_window_title(frame);
            }
        }
        saved
    }
}

impl eframe::App for EditorApp {
    /// Called each time the UI needs repainting, which may be many times per second.
    fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
        // Check session status
        if let Some(frame_count) = self.piece_moved {
            if frame_count != self.frame_counter {
                if self.session.finished {
                    super::show_info_message("提示", "绝杀");
                    // message_box.open();
                    // message_box.open_dialog(Some("提示"), Some("绝杀"), Some(Icon::Success));
                }
                self.piece_moved = None;
                self.update_window_title(frame);
            }
        }
        self.frame_counter = self.frame_counter.wrapping_add(1);

        let next_steps = self.session.get_next_steps();
        let has_next_steps = next_steps.len() > 0;
        let has_prev_steps = self.session.steps.len() > 0;
        let mut board_changed = false;
        let mut cursor_pos = None;

        if !self.is_dialog_open && !self.is_menu_open {
            // toggle show_next_steps
            ctx.input(|state| {
                if state.key_released(egui::Key::T) {
                    self.show_next_steps = !self.show_next_steps;
                }
                // toggle show_score
                if state.key_released(egui::Key::P) {
                    self.show_score = !self.show_score;
                }
                // rotate board vertically
                if state.key_released(egui::Key::F) {
                    self.ownside = self.ownside.opponent();
                }
                // mirror board horizontally
                if state.key_released(egui::Key::J) {
                    self.in_mirror_mode = !self.in_mirror_mode;
                    self.update_window_title(frame);
                }
                if state.key_released(egui::Key::Home) {
                    self.session.goto_start();
                    board_changed = true;
                }

                // key_pressed allows repeated invoke when the key is hold down
                if has_prev_steps && state.key_pressed(egui::Key::ArrowLeft) {
                    self.session.goto_prev_step();
                    board_changed = true;
                }
                if has_next_steps {
                    if state.key_pressed(egui::Key::ArrowRight) {
                        self.session.goto_next_step();
                        board_changed = true;
                    }
                    if let Some(mut num) = get_pressed_number(ctx) {
                        if state.modifiers.matches(egui::Modifiers::CTRL) {
                            num += 10;
                        }
                        self.session.enter_branch(num - 1);
                        board_changed = true;
                    }
                }
                if state.key_released(egui::Key::O) {
                    if state.modifiers.matches(egui::Modifiers::CTRL) {
                        self.open_book_file(frame);
                    }
                }
                if state.key_released(egui::Key::S) {
                    if state.modifiers.matches(egui::Modifiers::CTRL) {
                        self.save_book_file(Some(frame));
                    } else {
                        self.is_dialog_open = !self.is_dialog_open;
                        if self.is_dialog_open {
                            self.situation_name = if let Some((name, _)) = self
                                .session
                                .book
                                .get_named_situation(self.session.current_situation)
                            {
                                name.to_string()
                            } else {
                                String::new()
                            };
                        }
                    }
                }
                cursor_pos = state.pointer.hover_pos();
            });
            ctx.output_mut(|o| o.cursor_icon = egui::CursorIcon::None);
        }

        egui::CentralPanel::default().show(ctx, |ui| {
            let height = ui.available_height() - 28.0;
            let grid_size = height / 10.0;
            let width = grid_size * 9.0;
            let board_start = (ui.available_width() - width).max(0.0) * 0.5;
            // draw board
            self.draw_chess_board(ui.painter(), board_start, grid_size);
            // draw pieces
            let piece_size = grid_size * 0.9;
            let size = egui::Vec2::new(piece_size, piece_size);
            let mut selected_piece = self.selected_piece;
            for (pos, color, piece) in self.session.board.iter() {
                let center = self.piece_pos_to_image_pos(pos, board_start, grid_size);
                ui.allocate_ui_at_rect(egui::Rect::from_center_size(center, size), |ui| {
                    if color == self.session.turn {
                        let mut piece_button = self.get_piece_button(color, piece, piece_size);
                        if self.session.killing == Some(pos) {
                            piece_button = piece_button.tint(egui::Color32::LIGHT_RED);
                        }
                        if ui.add(piece_button).clicked() {
                            if !self.session.finished {
                                if selected_piece == Some(pos) {
                                    selected_piece = None;
                                } else {
                                    selected_piece = Some(pos);
                                }
                            }
                        }
                    } else {
                        ui.add(self.get_piece_image(color, piece, piece_size));
                    }
                });
            }
            self.selected_piece = selected_piece;

            // draw last step indicator
            if let Some((_, from, to, _)) = self.session.last_step() {
                let center = self.piece_pos_to_image_pos(from, board_start, grid_size);
                let indicator_color = egui::Color32::DARK_BLUE;
                let painter = ui.painter();
                painter.circle_filled(center, piece_size * 0.15, indicator_color);
                painter.circle_stroke(
                    center,
                    piece_size * 0.2,
                    egui::Stroke::new(1.0, indicator_color),
                );
                let center = self.piece_pos_to_image_pos(to, board_start, grid_size);
                painter.circle_stroke(
                    center,
                    piece_size * 0.5,
                    egui::Stroke::new(2.0, indicator_color),
                );
            }
            // draw branch arrows of next steps
            if has_next_steps && self.show_next_steps {
                let arrow_color = egui::Color32::LIGHT_GREEN;
                let dot_color = egui::Color32::from_rgba_unmultiplied(255, 115, 0, 200);
                let font_color = egui::Color32::WHITE;
                let size = egui::Vec2::new(piece_size * 0.3, piece_size * 0.3);
                let stroke = egui::Stroke::new(4.0, arrow_color);
                let font = egui::FontId::new(size.x * 0.8, egui::FontFamily::Proportional);
                let painter = ui.painter();
                let mut arrow_heads = Vec::with_capacity(next_steps.len());
                for step in next_steps {
                    let from = self.piece_pos_to_image_pos(step.from, board_start, grid_size);
                    let to = self.piece_pos_to_image_pos(step.to, board_start, grid_size);
                    let dir = to - from;
                    draw_arrow(painter, from, dir, piece_size * 0.4, stroke);

                    let mut n = 1.0;
                    let dot_pos = loop {
                        let new_pos = to - dir.normalized() * size.x * n;
                        if arrow_heads
                            .iter()
                            .any(|(pos, _): &(egui::Pos2, _)| pos.distance(new_pos) < size.x)
                        {
                            n += 1.0;
                        } else {
                            break new_pos;
                        }
                    };
                    arrow_heads.push((dot_pos, step));
                }
                for (index, (dot_pos, step)) in arrow_heads.into_iter().enumerate() {
                    ui.allocate_ui_at_rect(egui::Rect::from_center_size(dot_pos, size), |ui| {
                        let (response, painter) = ui.allocate_painter(size, egui::Sense::click());
                        painter.circle_filled(dot_pos, piece_size * 0.15, dot_color);
                        painter.text(
                            dot_pos,
                            egui::Align2::CENTER_CENTER,
                            if self.show_score && step.score > 0 {
                                step.tag().to_string()
                            } else {
                                (index + 1).to_string()
                            },
                            font.clone(),
                            font_color,
                        );
                        if response.clicked() {
                            self.move_piece(step.from, step.to);
                        }
                        response.context_menu(|ui| {
                            ctx.output_mut(|o| o.cursor_icon = egui::CursorIcon::Default);
                            ui.menu_button("评分", |ui| {
                                let mut score = 255;
                                for value in (0..10).rev() {
                                    if ui.button(get_tag(value).to_string()).clicked() {
                                        score = value;
                                    }
                                }
                                if score != 255 {
                                    self.session.set_score(step.from, step.to, score);
                                    self.show_score = true;
                                    ui.close_menu();
                                }
                            });
                            if ui.button("删除").clicked() {
                                if super::show_confirm_message(
                                    "确认",
                                    &format!("是否删除第{}种走法？", index + 1),
                                ) {
                                    self.session.delete_step(step.from, step.to);
                                    ui.close_menu();
                                }
                            };
                        });
                    });
                }
            }
            // draw selected status
            if let Some(pos) = self.selected_piece {
                let center = self.piece_pos_to_image_pos(pos, board_start, grid_size);
                ui.painter().circle_stroke(
                    center,
                    piece_size * 0.5,
                    egui::Stroke::new(3.0, egui::Color32::GREEN),
                );
                let size = egui::Vec2::new(piece_size * 0.3, piece_size * 0.3);
                for dest in self.session.board.possible_moves(pos) {
                    let center = self.piece_pos_to_image_pos(dest, board_start, grid_size);
                    ui.allocate_ui_at_rect(egui::Rect::from_center_size(center, size), |ui| {
                        let (response, painter) = ui.allocate_painter(size, egui::Sense::click());
                        painter.circle_filled(center, piece_size * 0.15, egui::Color32::GREEN);
                        if response.clicked() {
                            self.move_piece(pos, dest);
                        }
                    });
                }
            }

            egui::TopBottomPanel::bottom("my_panel")
                .frame(egui::Frame::none().fill(egui::Color32::from_gray(230)))
                .show(ctx, |ui| {
                    ui.set_height(30.0);
                    let height = 28.0;
                    ui.columns(7, |cols| {
                        if cols[1].add(self.get_button("menu", height)).clicked() {
                            self.is_menu_open = !self.is_menu_open;
                        }
                        if cols[2].add(self.get_button("start", height)).clicked() {
                            self.session.goto_start();
                            board_changed = true;
                        }
                        if cols[3].add(self.get_button("prev", height)).clicked() {
                            self.session.goto_prev_step();
                            board_changed = true;
                        }
                        if has_next_steps {
                            if cols[4].add(self.get_button("next", height)).clicked() {
                                self.session.goto_next_step();
                                board_changed = true;
                            }
                            if cols[5].add(self.get_button("branch", height)).clicked() {
                                self.session.goto_next_branch();
                                board_changed = true;
                            }
                        } else {
                            cols[4].add_enabled(
                                false,
                                self.buttons.get_disabled_button("next", height),
                            );
                            cols[5].add_enabled(
                                false,
                                self.buttons.get_disabled_button("branch", height),
                            );
                        }
                    });
                });

            // draw mouse cursor
            if let Some(pos) = cursor_pos {
                let size = egui::Vec2::new(32.0, 32.0);
                let rect = egui::Rect::from_min_size(pos, size);
                let cursor = self.cursor_image.as_ref().unwrap();
                ui.allocate_ui_at_rect(rect, |ui| {
                    ui.image(cursor, size);
                });
            }
        });
        if board_changed {
            self.selected_piece = None;
            self.piece_moved = Some(self.frame_counter);
        }

        if self.is_menu_open {
            self.show_book_dialog(ctx, frame);
        }
        if self.is_dialog_open {
            self.show_situation_dialog(ctx);
        }
    }

    fn on_close_event(&mut self) -> bool {
        if self.session.book_changed {
            if super::show_confirm_message("确认", "棋谱已更改，是否保存？") {
                return self.save_book_file(None);
            }
        }
        true
    }
}

/// Show an arrow starting at `origin` and going in the direction of `vec`, with the length `vec.length()`.
pub fn draw_arrow(
    painter: &egui::Painter,
    origin: egui::Pos2,
    vec: egui::Vec2,
    tip_length: f32,
    stroke: egui::Stroke,
) {
    use egui::emath::*;
    let rot = Rot2::from_angle(std::f32::consts::TAU / 10.0);
    let tip = origin + vec;
    let dir = vec.normalized();
    painter.line_segment([origin, tip], stroke);
    painter.line_segment([tip, tip - tip_length * (rot * dir)], stroke);
    painter.line_segment([tip, tip - tip_length * (rot.inverse() * dir)], stroke);
}

fn get_pressed_number(ctx: &egui::Context) -> Option<usize> {
    ctx.input(|input| {
        if input.key_released(egui::Key::Num1) {
            return Some(1);
        }
        if input.key_released(egui::Key::Num2) {
            return Some(2);
        }
        if input.key_released(egui::Key::Num3) {
            return Some(3);
        }
        if input.key_released(egui::Key::Num4) {
            return Some(4);
        }
        if input.key_released(egui::Key::Num5) {
            return Some(5);
        }
        if input.key_released(egui::Key::Num6) {
            return Some(6);
        }
        if input.key_released(egui::Key::Num7) {
            return Some(7);
        }
        if input.key_released(egui::Key::Num8) {
            return Some(8);
        }
        if input.key_released(egui::Key::Num9) {
            return Some(9);
        }
        if input.key_released(egui::Key::Num0) {
            return Some(10);
        }
        return None;
    })
}
